{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "169f4dcd",
   "metadata": {},
   "source": [
    "![image.png](https://langgraph.com.cn/concepts/img/multi_agent/architectures.png)\n",
    "\n",
    "在多代理系统中，有几种连接代理的方法：\n",
    "\n",
    "- 网络：每个代理都可以与所有其他代理通信。任何代理都可以决定下一步调用哪个其他代理。\n",
    "- 主管：每个代理都与一个主管代理通信。主管代理决定下一步应该调用哪个代理。\n",
    "- 主管（工具调用）：这是主管架构的一个特例。单个代理可以表示为工具。在这种情况下，主管代理使用一个支持工具调用的 LLM 来- 决定调用哪个代理工具，以及传递给这些代理的参数。\n",
    "- 分层：你可以定义一个具有主管之主管的多代理系统。这是主管架构的泛化，允许更复杂的控制流。\n",
    "- 自定义多代理工作流：每个代理只与部分代理通信。流程的部分是确定性的，并且只有某些代理可以决定下一步调用哪个其他代理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4a4bf95",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "来自子图 flight_assistant 的更新:\n",
      "\n",
      "\n",
      "\t来自节点 agent 的更新:\n",
      "\n",
      "\n",
      "\t==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\tName: flight_assistant\n",
      "\tTool Calls:\n",
      "\t  book_flight (call_OGAH5bKUOUfOEVvHn1ZWK1Bv)\n",
      "\t Call ID: call_OGAH5bKUOUfOEVvHn1ZWK1Bv\n",
      "\t  Args:\n",
      "\t    from_airport: BOS\n",
      "\t    to_airport: JFK\n",
      "\n",
      "\n",
      "来自子图 flight_assistant 的更新:\n",
      "\n",
      "\n",
      "\t来自节点 tools 的更新:\n",
      "\n",
      "\n",
      "\t=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\tName: book_flight\n",
      "\t\n",
      "\t已成功预订从 BOS 到 JFK 的航班。\n",
      "\n",
      "\n",
      "来自子图 flight_assistant 的更新:\n",
      "\n",
      "\n",
      "\t来自节点 agent 的更新:\n",
      "\n",
      "\n",
      "\t==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\tName: flight_assistant\n",
      "\tTool Calls:\n",
      "\t  transfer_to_hotel_assistant (call_wXTw49n3OZrtq3ofjmup4XEz)\n",
      "\t Call ID: call_wXTw49n3OZrtq3ofjmup4XEz\n",
      "\t  Args:\n",
      "\n",
      "\n",
      "来自子图 hotel_assistant 的更新:\n",
      "\n",
      "\n",
      "\t来自节点 agent 的更新:\n",
      "\n",
      "\n",
      "\t==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\tName: hotel_assistant\n",
      "\tTool Calls:\n",
      "\t  book_hotel (call_xCPJgbjkMkjrCkBK5w5t2M17)\n",
      "\t Call ID: call_xCPJgbjkMkjrCkBK5w5t2M17\n",
      "\t  Args:\n",
      "\t    hotel_name: McKittrick Hotel\n",
      "\n",
      "\n",
      "来自子图 hotel_assistant 的更新:\n",
      "\n",
      "\n",
      "\t来自节点 tools 的更新:\n",
      "\n",
      "\n",
      "\t=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\tName: book_hotel\n",
      "\t\n",
      "\t已成功预订 McKittrick Hotel 的住宿。\n",
      "\n",
      "\n",
      "来自子图 hotel_assistant 的更新:\n",
      "\n",
      "\n",
      "\t来自节点 agent 的更新:\n",
      "\n",
      "\n",
      "\t==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\tName: hotel_assistant\n",
      "\t\n",
      "\t两项预订已完成，信息如下：\n",
      "\t\n",
      "\t- 航班：波士顿 (BOS) → 纽约 (JFK) 已成功预订\n",
      "\t- 酒店：McKittrick Hotel 已成功预订\n",
      "\t\n",
      "\t如果您需要，我可以提供或发送以下信息：\n",
      "\t- 航班详情（航班号、日期、起降时间、航空公司、座位/舱位等）以及登机提醒\n",
      "\t- 酒店预订的房型、入住/退房日期、预订号、入住政策\n",
      "\t- 机场接送、网约车或酒店交通方案\n",
      "\t- 更改/取消政策、行李规定等\n",
      "\t\n",
      "\t请告知您还想要的细节（如具体日期、房型偏好、座位偏好），或需要我发送确认信息到邮箱/短信。\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# -*- coding: utf-8 -*-\n",
    "\"\"\"\n",
    "用于旅行预定的多智能体系统 (MAS)\n",
    "此系统包含一个航班预订代理和一个酒店预订代理，它们可以通过相互移交来协同完成用户的综合预订请求。\n",
    "\"\"\"\n",
    "\n",
    "# 导入必要的模块\n",
    "from typing import Annotated  # 用于为函数参数添加类型注解\n",
    "\n",
    "# LangChain 相关模块\n",
    "from langchain_core.messages import convert_to_messages  # 将消息列表转换为标准格式\n",
    "from langchain_core.tools import tool, InjectedToolCallId  # 定义工具函数和注入工具调用ID\n",
    "\n",
    "# LangGraph 核心模块\n",
    "from langgraph.prebuilt import create_react_agent, InjectedState  # 创建基于ReAct模式的智能体，注入状态\n",
    "from langgraph.graph import StateGraph, START, MessagesState    # 构建状态图，定义起始节点，消息状态类型\n",
    "from langgraph.types import Command                            # 用于控制流程跳转的命令\n",
    "\n",
    "\n",
    "# ------------------- 辅助函数：美化输出 -------------------\n",
    "def pretty_print_message(message, indent=False):\n",
    "    \"\"\"\n",
    "    美化单条消息的打印输出。\n",
    "    \n",
    "    Args:\n",
    "        message: 要打印的消息对象。\n",
    "        indent (bool): 是否添加缩进（用于子图）。\n",
    "    \"\"\"\n",
    "    pretty_message = message.pretty_repr(html=True)  # 获取消息的美化表示\n",
    "    if not indent:\n",
    "        print(pretty_message)\n",
    "        return\n",
    "\n",
    "    indented = \"\\n\".join(\"\\t\" + c for c in pretty_message.split(\"\\n\"))\n",
    "    print(indented)\n",
    "\n",
    "\n",
    "def pretty_print_messages(update, last_message=False):\n",
    "    \"\"\"\n",
    "    美化并打印整个更新流中的消息，使其结构清晰易读。\n",
    "    \n",
    "    Args:\n",
    "        update: 来自图执行流的更新数据。\n",
    "        last_message (bool): 是否只打印最后一条消息。\n",
    "    \"\"\"\n",
    "    is_subgraph = False\n",
    "    if isinstance(update, tuple):\n",
    "        ns, update = update\n",
    "        # 跳过主图的更新，专注于子图\n",
    "        if len(ns) == 0:\n",
    "            return\n",
    "\n",
    "        graph_id = ns[-1].split(\":\")[0]\n",
    "        print(f\"来自子图 {graph_id} 的更新:\")\n",
    "        print(\"\\n\")\n",
    "        is_subgraph = True\n",
    "\n",
    "    for node_name, node_update in update.items():\n",
    "        update_label = f\"来自节点 {node_name} 的更新:\"\n",
    "        if is_subgraph:\n",
    "            update_label = \"\\t\" + update_label\n",
    "\n",
    "        print(update_label)\n",
    "        print(\"\\n\")\n",
    "\n",
    "        messages = convert_to_messages(node_update[\"messages\"])\n",
    "        if last_message:\n",
    "            messages = messages[-1:]\n",
    "\n",
    "        for m in messages:\n",
    "            pretty_print_message(m, indent=is_subgraph)\n",
    "        print(\"\\n\")\n",
    "\n",
    "# 移交机制 (Handoff Pattern)：\n",
    "\n",
    "# 这是本系统的关键。我们没有一个中央调度器，而是让每个代理都拥有一个“移交”工具。\n",
    "# create_handoff_tool 函数是一个工厂，它能生成一个特殊的 tool。\n",
    "\n",
    "# 当一个代理调用这个工具时，它不会返回一个简单的字符串，而是返回一个 Command 对象。\n",
    "\n",
    "# 这个 Command 对象指示 LangGraph 的执行引擎：\n",
    "# “停止在当前节点运行，跳转到 goto 指定的下一个节点，并带上更新后的状态”。\n",
    "\n",
    "# ------------------- 创建移交工具 (Handoff Tools) -------------------\n",
    "def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
    "    \"\"\"\n",
    "    工厂函数：创建一个可以将控制权移交给指定代理的特殊工具。\n",
    "    这是实现多代理协作的核心机制。\n",
    "    \n",
    "    Args:\n",
    "        agent_name (str): 目标代理的名称。\n",
    "        description (str, optional): 该工具的描述。\n",
    "        \n",
    "    Returns:\n",
    "        function: 一个可被智能体调用的工具函数。\n",
    "    \"\"\"\n",
    "    name = f\"transfer_to_{agent_name}\"\n",
    "    description = description or f\"转移到 {agent_name}\"\n",
    "\n",
    "    @tool(name, description=description)\n",
    "    def handoff_tool(\n",
    "        state: Annotated[MessagesState, InjectedState],           # 注入当前对话状态\n",
    "        tool_call_id: Annotated[str, InjectedToolCallId],         # 注入本次工具调用的ID\n",
    "    ) -> Command:\n",
    "        # 创建一条工具执行结果的消息\n",
    "        tool_message = {\n",
    "            \"role\": \"tool\",\n",
    "            \"content\": f\"成功转移到 {agent_name}\",\n",
    "            \"name\": name,\n",
    "            \"tool_call_id\": tool_call_id,\n",
    "        }\n",
    "        return Command(  \n",
    "            goto=agent_name,             # 命令：下一步跳转到名为 agent_name 的节点\n",
    "            update={\"messages\": state[\"messages\"] + [tool_message]},  # 更新：将新消息追加到历史记录中\n",
    "            graph=Command.PARENT,        # 指定跳转发生在父图层级\n",
    "        )\n",
    "    return handoff_tool\n",
    "\n",
    "# 创建具体的移交工具实例\n",
    "transfer_to_hotel_assistant = create_handoff_tool(\n",
    "    agent_name=\"hotel_assistant\",\n",
    "    description=\"将用户转接给酒店预订助理。\",\n",
    ")\n",
    "transfer_to_flight_assistant = create_handoff_tool(\n",
    "    agent_name=\"flight_assistant\",\n",
    "    description=\"将用户转接给航班预订助理。\",\n",
    ")\n",
    "\n",
    "\n",
    "# ------------------- 定义基础功能工具 -------------------\n",
    "def book_hotel(hotel_name: str):\n",
    "    \"\"\"模拟预订酒店的操作\"\"\"\n",
    "    return f\"已成功预订 {hotel_name} 的住宿。\"\n",
    "\n",
    "def book_flight(from_airport: str, to_airport: str):\n",
    "    \"\"\"模拟预订航班的操作\"\"\"\n",
    "    return f\"已成功预订从 {from_airport} 到 {to_airport} 的航班。\"\n",
    "\n",
    "\n",
    "# ------------------- 创建智能代理 (Agents) -------------------\n",
    "# 使用 create_react_agent 快速创建两个具备思考（ReAct）能力的智能体\n",
    "\n",
    "flight_assistant = create_react_agent(\n",
    "    model=\"openai:gpt-5-nano\",  # 使用的模型（示例）\n",
    "    tools=[book_flight, transfer_to_hotel_assistant],  # 该代理可用的工具集\n",
    "    prompt=\"你是一位专业的航班预订助理。你的任务是帮助用户预订航班。如果用户还需要预订酒店，请使用 transfer_to_hotel_assistant 工具将他们转接给酒店助理。\",  # 中文系统提示词\n",
    "    name=\"flight_assistant\"  # 代理名称\n",
    ")\n",
    "\n",
    "hotel_assistant = create_react_agent(\n",
    "    model=\"openai:gpt-5-nano\",\n",
    "    tools=[book_hotel, transfer_to_flight_assistant],\n",
    "    prompt=\"你是一位专业的酒店预订助理。你的任务是帮助用户预订酒店。如果用户还需要预订航班，请使用 transfer_to_flight_assistant 工具将他们转接给航班助理。\",  # 中文系统提示词\n",
    "    name=\"hotel_assistant\"\n",
    ")\n",
    "\n",
    "\n",
    "# ------------------- 构建多代理工作流图 -------------------\n",
    "# 创建一个以消息状态为基础的状态图\n",
    "multi_agent_graph = (\n",
    "    StateGraph(MessagesState)               # 初始化状态图为消息驱动\n",
    "    .add_node(flight_assistant)             # 添加航班助理节点\n",
    "    .add_node(hotel_assistant)              # 添加酒店助理节点\n",
    "    .add_edge(START, \"flight_assistant\")    # 定义初始边：流程从航班助理开始\n",
    "    .compile()                              # 编译图，使其可执行\n",
    ")\n",
    "\n",
    "\n",
    "# ------------------- 执行任务与查看结果 -------------------\n",
    "# 向多代理系统提交一个包含多项任务的复杂请求\n",
    "for chunk in multi_agent_graph.stream(\n",
    "    {\n",
    "        \"messages\": [\n",
    "            {\n",
    "                \"role\": \"user\",\n",
    "                \"content\": \"请帮我预订一张从波士顿(BOS)到纽约(JFK)的机票，以及在麦克基特里克酒店(McKittrick Hotel)的住宿。\"\n",
    "            }\n",
    "        ]\n",
    "    },\n",
    "    subgraphs=True  # 包含子图的详细信息\n",
    "):\n",
    "    # 流式打印每一步的执行情况\n",
    "    pretty_print_messages(chunk)\n",
    "\n",
    "\n",
    "# 执行过程：\n",
    "# 当用户提交一个包含航班和酒店需求的请求时，流程从 flight_assistant 开始。\n",
    "# flight_assistant 在其 prompt 的指导下，识别出除了航班还有酒店需要预订。\n",
    "# 因此，它会调用 transfer_to_hotel_assistant 工具。\n",
    "# 这个工具的执行会触发一个 Command，导致执行引擎将控制权移交给 hotel_assistant 节点。\n",
    "# hotel_assistant 接收到完整的对话历史，看到用户的需求，于是调用 book_hotel 工具完成预订。\n",
    "# （在此示例中，流程会在酒店预订后结束。如果需要更复杂的循环，\n",
    "# 可以设计 hotel_assistant 在完成后也移交回 flight_assistant 或其他节点。）"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "MLOps",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
